package com.ying.tests;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class GoodCodeTest {
    enum AccountType {
        SAVING, FIXED, CURRENT;
        private AccountType() {
            System.out.println("It is a account type");
        }
    }
    public static void main(String[] args) {
        // set的add方法，可以直接知道该值是否存在，避免调用contains方法判断。
//        test1();        //使用ImmutableMap替代switch💕
//        test2();        //定义List的几种方式（也可以定义Map、Set ...）
//        test3();        //list指定大小切分
//        test4();        //利用 Optional —— 保证值存在
//        test5();        //利用 Optional —— 保证值合法，在范围内
//        test6();        //利用 Optional —— 避免空判断，且用@Builder的构造器模式
//        test7();        //利用 Stream —— 汇总集合数据
//        test8();        //利用 Stream —— 转化集合数据
//        test9();        //利用 Stream —— 分组
//        test10();       //简化异常断言
//        test11();       //简化数据拷贝
//        test12();       //利用 Stream —— test7()+test9()的stream汇总与分组结合
//        test13();       //利用 Stream —— 生成集合范围


//        System.out.println(AccountType.FIXED);
    }


    /**
     * 利用 Stream —— 生成集合范围
     */
    private static void test13() {
        int N = 10;
        // 方法一：普通：循环生成
//        int[] arr1 = new int[N];
//        for (int i = 0; i < N; i++) {
//            arr1[i] = i + 1;
//        }
//        Arrays.stream(arr1).forEach(System.out::println);
//
//        int[] arr2 = new int[N];
//        arr2[0] = 1;
//        for (int i = 1; i < N; i++) {
//            arr2[i] = arr2[i - 1] * 2;
//        }
//        Arrays.stream(arr2).forEach(System.out::println);
        // 方法二：精通：stream一键生成，类似于Python的range
        int[] ints1 = IntStream.rangeClosed(1, N).toArray();
        Arrays.stream(ints1).forEach(System.out::println);
        int[] ints2 = IntStream.iterate(1, i -> i * 2).limit(N).toArray();
        Arrays.stream(ints2).forEach(System.out::println);

    }

    /**
     * 利用 Stream —— test7()+test9()的stream汇总与分组结合p
     */
    private static void test12() {
        List<Account> list = ImmutableList.<Account>builder()
                .add(Account.builder().roleId(1L).balance(1.11).build())
                .add(Account.builder().roleId(1l).balance(2.22).build())
                .add(Account.builder().roleId(1l).balance(3.33).build())
                .add(Account.builder().roleId(2l).balance(4.44).build())
                .add(Account.builder().roleId(3l).balance(5.55).build())
                .build();
        // 方法一：普通：循环取roleId分组并累加balance
        Map<Long, Double> map = new HashMap<>();
//        for (Account account : list) {
//            Long roleId = account.getRoleId();
//            Double total = Optional.ofNullable(map.get(roleId)).orElse(0.0d);
//            map.put(roleId, total + account.getBalance());
//        }
//        System.out.println(map);
        // 方法二：精简:stream分组并求和每组薪资
        Map<Long, Double> map2 = list.stream().collect(Collectors.groupingBy(Account::getRoleId, Collectors.summingDouble(Account::getBalance)));
        System.out.println(map2);
    }

    /**
     * 简化数据拷贝
     */
    private static void test11() {
        // 方法一：普通：把user1拷贝给user2
        List<User2> list = new ArrayList<>();
        User1 user1 = User1.builder().roleId(111L).name("小明").pass("123").build();
//        User2 user2 = User2.builder().roleId(user1.getRoleId()).username(user1.getName()).password(user1.getPass()).build();
//        list.add(user2);
        // 方法二：精简：使用BeanUtils实现浅拷贝
        User2 user2 = User2.builder().username("小颖").password("123").build();
        BeanUtils.copyProperties(user1, user2);
        list.add(user2);
        System.out.println(list);
        // 反例（不能以过大的性能损失为代价，例子是浅层拷贝，用不着JSON这样的重量级武器）
//        List<User2> result = JSON.parseArray(JSON.toJSONString(list), User2.class);
//        System.out.println(result);
    }

    /**
     * 简化异常断言
     */
    private static void test10() {
        Long userId = 111L;
        // 方法一：普通：直接判断
        if (Objects.isNull(userId)) {
            throw new IllegalArgumentException("用户标识不能为空");
        }
        // 方法二：精简：断言判断并返回信息（注意：可能有写插件不认同这种判断，导致使用该对象时会有空指针警告）
        Assert.notNull(userId, "用户标识不能为空");
    }

    /**
     * 利用 Stream —— 分组（利用Map的 computeIfAbsent 方法，可以保证获取到的对象非空，从而避免了不必要的空判断和重新设置值。）
     */
    private static void test9() {
        Map<Long, List<User1>> user1Map = new HashMap<>();
        List<User1> user1List = new ArrayList<>();
        user1List.add(User1.builder().roleId(Long.valueOf(1)).name("小明").pass("123").build());
        user1List.add(User1.builder().roleId(Long.valueOf(1)).name("小颖").pass("123").build());
        user1List.add(User1.builder().roleId(Long.valueOf(2)).name("小刚").pass("123").build());
        user1List.add(User1.builder().roleId(Long.valueOf(3)).name("小霞").pass("123").build());
        user1List.add(User1.builder().roleId(Long.valueOf(3)).name("小智").pass("123").build());
        // 方法一：普通：循环判空并存储map
//        for (User1 user1 : user1List) {
//            Long roleId = user1.getRoleId();
//            List<User1> userList = user1Map.get(roleId);
//            if (Objects.isNull(userList)) {
//                userList = new ArrayList<>();
//                user1Map.put(roleId, userList);
//            }
//            userList.add(user1);
//        }
        // 方法二：精简：循环直接存储
//        for (User1 user1 : user1List) {
//            user1Map.computeIfAbsent(user1.getRoleId(), key -> new ArrayList<>()).add(user1);
//        }
//        System.out.println(user1Map.toString());
        // 方法三：再精简：直接分组存储
        Map<Long, List<User1>> user1Map2 = user1List.stream().collect(Collectors.groupingBy(User1::getRoleId));
        System.out.println(user1Map2);
    }

    /**
     * 利用 Stream —— 转化集合数据
     */
    private static void test8() {
        // 方法一：普通：将list1的user1的对象经过循环数据转换为list2的user2对象
        List<User1> list1 = new ArrayList<User1>();
        list1.add(User1.builder().name("小刚").pass("123").build());
        list1.add(User1.builder().name("小智").pass("123").build());
        list1.add(User1.builder().name("小霞").pass("123").build());
        List<User2> list2 = new ArrayList<User2>();
        for (User1 user1 : list1) {
            list2.add(transUser(user1));
        }
        System.out.println(list2.toString());
        // 方法二：精简：直接转为map并转换
        List<User2> list3 = list1.stream().map(GoodCodeTest::transUser).collect(Collectors.toList());
        System.out.println(list3);

    }
    private static User2 transUser(User1 user1) {
        return User2.builder().username(user1.getName()).password(user1.getPass()).build();
    }

    /**
     * 利用Stream —— 汇总集合数据
     */
    private static void test7() {
        double total = 0.111d;
        List<Account> list = ImmutableList.<Account>builder()
                .add(Account.builder().balance(1.11).build())
                .add(Account.builder().balance(2.22).build())
                .add(Account.builder().balance(3.33).build())
                .add(Account.builder().balance(4.44).build())
                .add(Account.builder().balance(5.55).build())
                .build();
        // 方法一：普通：循环累加
        for (Account account : list) {
            total += account.getBalance();
        }
        System.out.println(total);
        // 方法二：精简：sum函数累加
        double total2 = list.stream().mapToDouble(Account::getBalance).sum();
        System.out.println(total2);
    }

    /**
     * 利用 Optional —— 避免空判断，且用@Builder的构造器模式
     */
    private static void test6() {
        String zipCode = null;
        User user = User.builder().address(
                Address.builder().country(
                        Country.builder().zipCode("666").build()
                ).build()
        ).build();
        // 方法一：普通：判断三层并取值
        if (Objects.nonNull(user)) {
            Address address = user.getAddress();
            if (Objects.nonNull(address)) {
                Country country = address.getCountry();
                if (Objects.nonNull(country)) {
                    if (Objects.nonNull(country)) {
                        zipCode = country.getZipCode();
                        System.out.println(zipCode);
                    }
                }
            }
        }
        //方法二：精简：三层直接转为map方法引用取值并赋默认值
        String s = Optional.ofNullable(user)
                .map(User::getAddress)
                .map(Address::getCountry)
                .map(Country::getZipCode)
                .orElse("没有值");
        System.out.println(s);


    }

    /**
     * 利用 Optional —— 保证值合法，在范围内
     */
    private static void test5() {
        // 方法一：if判空+条件并赋默认值
        Integer value = 10, i = 50, MAX_VALUE = 100;
        if (Objects.nonNull(value) && value.compareTo(MAX_VALUE) == -1) {
            value = i;
        } else {
            value = MAX_VALUE;
        }
        System.out.println(value);
        // 方法二：精简：判空并过滤并赋默认值
        value = Optional.ofNullable(value).filter(v -> v.compareTo(MAX_VALUE) == -1).orElse(MAX_VALUE);
        System.out.println(value);
    }

    /**
     * 利用 Optional —— 保证值存在
     */
    private static void test4() {
        // 方法一：普通：if判空并重新赋默认值
        Integer value = 10, i = 50, DEFAULT_VALUE = 0;
        if (Objects.nonNull(value)) {
            value = i;
        } else {
            value = DEFAULT_VALUE;
        }
        System.out.println(value);
        // 方法二：精简：判空并指定默认值
        value = Optional.ofNullable(value).orElse(DEFAULT_VALUE);
        System.out.println(value);
    }

    /**
     * list指定大小切分
     */
    private static void test3() {
        List<String> list = ImmutableList.of("明明", "颖颖", "张三", "李四", "王五", "赵六");
        System.out.println(list.toString());
        // 方法一：普通：循环按下标切分
//        List<List<String>> list2 = new ArrayList<>();
//        for (int i = 0; i < list.size(); i += 2) {
//            list2.add(list.subList(i, Math.min(i + 2, list.size())));
//        }
//        System.out.println(list2.toString());
        // 方法二：精简：用工具类切分
        List<List<String>> list2 = Lists.partition(list, 5);
        System.out.println(list2);
    }

    /**
     * 定义List的几种方式（也可以定义Map、Set ...）
     */
    private static void test2() {
        // JDK流派
        List<String> list1 = Arrays.asList("dog", "cat", "tiger");
        // Guava流派（它的copyOf方法可以将set转list 。。。）
        List<String> list2 = ImmutableList.of("dog", "cat", "tiger");
    }

    /**
     * 使用ImmutableMap替代switch💕
     */
    private static void test1() {
        String name = "";
        // 方法一：普通：使用switch
        getName1(name);
        // 方法二：精简：使用ImmutableMap精简
        getName2(name);
    }
    private static String getName1(String name) {
        switch (name) {
            case "dog" :
            case "cat" :
                return "animal";
            case "lavender" :
                return "plant";
            default :
                return null;
        }
    }
    private static String getName2(String name) {
        Map<String, String> map = ImmutableMap.<String, String>builder()
                .put("dog", "animal")
                .put("cat", "animal")
                .put("lavender", "plant")
                .build();
        // 写法一：
        Map<String, String> map1 = new ImmutableMap.Builder<String, String>()
                .put("dog", "animal")
                .put("cat", "animal")
                .build();
        // 写法二：
        Map<String, String> map2 = ImmutableMap.<String, String>builder()
                .put("dog", "animal")
                .put("cat", "animal")
                .build();
        return map.get(name);
    }

}

@Data
@Builder
class User {
    private Address address;
}

@Data
@Builder
class Country {
    private String zipCode;
}

@Data
@Builder
class Address {
    private Country country;
}

@Data
@Builder
class Account {
    private Long roleId;
    private double balance;
}

@Data
@Builder
@ToString
class User1 {
    private Long roleId;
    private String name;
    private String pass;
}

@Data
@Builder
@ToString
class User2 {
    private Long roleId;
    private String username;
    private String password;
}